가상 면접 사례로 배우는 생성형 AI 시스템 설계 — 0장 선수지식 딥다이브 학습 노트
출처: 가상 면접 사례로 배우는 생성형 AI 시스템 설계 (알리 아미니안, 알렉스 쉬) | 참고: https://www.aliaminian.com/books
전체 흐름도
┌──────────────────────────────────────────────────────────────┐
│ 선수지식 → 생성형 AI 연결 맵 │
├──────────────────────────────────────────────────────────────┤
│ │
│ [0.1 확률/통계] │
│ └─ 조건부확률, 베이즈 → P(Y|X) 판별형, P(X) 생성형 │
│ │
│ [0.2 선형대수] │
│ └─ 행렬 곱, 내적 → 어텐션 Q·K^T, 가중치 행렬 W │
│ │
│ [0.3 머신러닝 기초] │
│ └─ 지도/비지도/자기지도 → 사전학습, 미세조정 이해 │
│ │
│ [0.4 Python/numpy] │
│ └─ 배열 연산, 브로드캐스팅 → 모든 코드 예제의 기반 │
│ │
│ [0.5 딥러닝 기초] │
│ └─ 신경망, 역전파, 활성화 → 트랜스포머, GAN, 확산 모델 │
│ │
└──────────────────────────────────────────────────────────────┘
선수 지식 체크리스트
- [ ] 확률의 기본 (사건, 조건부 확률, 독립)
- [ ] 베이즈 정리 (사전확률, 우도, 사후확률)
- [ ] 벡터와 행렬 연산 (덧셈, 곱셈, 전치, 내적)
- [ ] softmax 함수의 의미와 계산
- [ ] 지도학습, 비지도학습, 자기 지도 학습 구분
- [ ] numpy 기본 (배열 생성, 인덱싱, 연산)
- [ ] 신경망 기본 구조 (입력층, 은닉층, 출력층)
- [ ] 활성화 함수 (Sigmoid, ReLU)
- [ ] 역전파와 경사 하강법 원리
핵심 키워드
| 용어 | 의미 |
|---|---|
| 확률 (Probability) | 어떤 사건이 일어날 가능성을 0~1 사이 숫자로 표현 |
| 조건부 확률 P(A|B) | B가 일어났다는 조건 하에 A가 일어날 확률 |
| 베이즈 정리 | 새로운 증거를 바탕으로 사전 확률을 업데이트하는 공식 |
| 정규 분포 | 종 모양의 대칭 확률 분포. 자연현상에서 가장 흔함 |
| 벡터 (Vector) | 크기와 방향을 가진 양. 1차원 배열로 표현 |
| 행렬 (Matrix) | 숫자를 2차원으로 배열한 것. 데이터 변환에 사용 |
| 내적 (Dot Product) | 두 벡터의 원소별 곱의 합. 유사도 측정에 사용 |
| 전치 (Transpose) | 행과 열을 뒤바꾸는 연산 |
| softmax | 실수 벡터를 확률 분포(합=1)로 변환하는 함수 |
| 지도 학습 | 정답(레이블)이 있는 데이터로 학습 |
| 비지도 학습 | 정답 없이 데이터의 패턴을 스스로 발견 |
| 자기 지도 학습 | 데이터 자체에서 학습 신호를 만들어 학습 (예: 빈칸 채우기) |
| 과적합 (Overfitting) | 학습 데이터에는 잘 맞지만 새 데이터에는 못 맞는 상태 |
| 뉴런 (Neuron) | 신경망의 기본 단위. 입력의 가중합 + 활성화 함수 |
| 활성화 함수 | 뉴런의 출력을 비선형으로 변환 (Sigmoid, ReLU 등) |
| 역전파 (Backpropagation) | 출력의 오차를 역방향으로 전파하여 가중치를 업데이트 |
| 경사 하강법 (Gradient Descent) | 손실 함수의 기울기 방향으로 매개변수를 조금씩 조정 |
| 손실 함수 (Loss Function) | 모델 예측과 실제 값의 차이를 측정하는 함수 |
| 학습률 (Learning Rate) | 한 번의 업데이트에서 매개변수를 얼마나 바꿀지 결정하는 값 |
| 에포크 (Epoch) | 전체 학습 데이터를 한 번 다 사용하는 학습 주기 |
0.1 확률과 통계 기초
한 줄 요약
확률은 "불확실한 것을 숫자로 표현"하는 도구이며, 베이즈 정리는 "새 증거를 보고 생각을 업데이트"하는 방법이다.
쉬운 설명 (비유로 풀어쓰기)
확률 = 날씨 예보 - "내일 비 올 확률 70%" → 완전히 확실하지 않지만 숫자로 표현 - 0 = 절대 안 일어남, 1 = 반드시 일어남
조건부 확률 = "우산을 가져간 사람 중에서 비를 맞은 사람의 비율" - P(비|우산) = "우산을 가져갔는데 비가 올 확률" - P(우산|비) = "비가 오는데 우산을 가져온 확률" (다른 값!)
베이즈 정리 = 의사의 진단 업데이트 - 처음 생각: "이 질병은 1000명 중 1명" (사전 확률) - 검사 양성 나옴 → "그래도 실제 확률은 9% 정도" (사후 확률) - 왜? 검사가 100% 정확하지 않으니까 (위양성 존재)
실무 예제
import numpy as np
# === 1. 동전 던지기 시뮬레이션 ===
np.random.seed(42)
flips = np.random.choice(["앞", "뒤"], size=10000)
head_ratio = np.sum(flips == "앞") / len(flips)
print(f"동전 10000번: 앞면 비율 = {head_ratio:.4f} (이론: 0.5)")
# === 2. 조건부 확률: 스팸 필터 ===
total = 1000
spam = 200
has_free = 150
spam_and_free = 120
# P(스팸 | "무료" 포함)
p_spam_given_free = spam_and_free / has_free
print(f"\nP(스팸 | '무료' 포함) = {p_spam_given_free:.2f}") # 0.80
# P("무료" 포함 | 스팸)
p_free_given_spam = spam_and_free / spam
print(f"P('무료' 포함 | 스팸) = {p_free_given_spam:.2f}") # 0.60
# → 두 값이 다르다! 조건부 확률의 방향이 중요
# === 3. 베이즈 정리: 질병 검사 ===
# 질병 유병률 0.1% (1000명 중 1명)
p_disease = 0.001
# 검사 민감도 99% (질병 있으면 양성 나올 확률)
p_positive_given_disease = 0.99
# 위양성률 5% (질병 없는데 양성 나올 확률)
p_positive_given_no_disease = 0.05
# 베이즈 정리로 P(질병 | 양성) 계산
p_positive = (p_positive_given_disease * p_disease +
p_positive_given_no_disease * (1 - p_disease))
p_disease_given_positive = (p_positive_given_disease * p_disease) / p_positive
print(f"\n[베이즈 정리] 검사 양성일 때 실제 질병 확률: {p_disease_given_positive:.4f}")
print(f"→ 약 {p_disease_given_positive*100:.1f}% (99%가 아니라 ~2%!)")
# === 4. 정규 분포 ===
# 평균=0, 표준편차=1인 정규분포에서 1000개 샘플
samples = np.random.normal(loc=0, scale=1, size=1000)
print(f"\n정규분포 샘플 1000개: 평균={np.mean(samples):.3f}, 표준편차={np.std(samples):.3f}")
# === 5. 기대값과 분산 ===
# 주사위의 기대값
die = np.array([1, 2, 3, 4, 5, 6])
probs = np.ones(6) / 6 # 각 1/6
expected = np.sum(die * probs)
variance = np.sum((die - expected)**2 * probs)
print(f"\n주사위 기대값: {expected:.2f} (이론: 3.5)")
print(f"주사위 분산: {variance:.2f} (이론: 2.917)")
핵심 체크포인트
- ✅ 조건부 확률 P(A|B) ≠ P(B|A) — 방향이 다르면 값도 다르다
- ✅ 베이즈 정리: P(A|B) = P(B|A)·P(A) / P(B) — 새 증거로 믿음을 업데이트
- ✅ 생성형 AI에서 확률은 "다음 토큰이 나올 가능성"을 계산하는 핵심
0.2 선형대수 기초
한 줄 요약
벡터는 "방향이 있는 숫자 목록", 행렬은 "변환 도구"이며, 이 둘의 곱셈이 신경망의 핵심 연산이다.
쉬운 설명 (비유로 풀어쓰기)
벡터 = 쇼핑 목록 - [사과 3개, 바나나 2개, 우유 1개] → 3차원 벡터 [3, 2, 1] - 각 숫자가 하나의 "특성"을 나타냄
행렬 = 변환 레시피 - "이 재료들로 케이크를 만들려면..." → 재료(벡터)에 레시피(행렬)를 적용하면 결과물(새 벡터) - 신경망에서 가중치 행렬 W가 바로 이 "변환 레시피"
내적(Dot Product) = 두 사람의 취향 비교 - A가 좋아하는 것 [영화 5점, 음악 3점, 운동 1점] - B가 좋아하는 것 [영화 4점, 음악 1점, 운동 5점] - 내적 = 5×4 + 3×1 + 1×5 = 28 → 숫자가 클수록 취향이 비슷! - 어텐션에서 Q·K가 바로 이 내적 → "이 단어들이 얼마나 관련있는지" 점수
실무 예제
import numpy as np
# === 1. 벡터 기본 ===
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("=== 벡터 연산 ===")
print(f"a = {a}")
print(f"b = {b}")
print(f"a + b = {a + b}") # [5, 7, 9]
print(f"a * 3 = {a * 3}") # [3, 6, 9] 스칼라 곱
print(f"a * b = {a * b}") # [4, 10, 18] 원소별 곱
print(f"내적 a·b = {np.dot(a, b)}") # 32
# === 2. 행렬 기본 ===
W = np.array([[1, 2],
[3, 4],
[5, 6]]) # 3×2 행렬
x = np.array([10, 20]) # 2차원 입력 벡터
print("\n=== 행렬 × 벡터 ===")
print(f"W (3×2):\n{W}")
print(f"x (2,): {x}")
print(f"W @ x = {W @ x}") # [50, 110, 170] → 3차원 출력!
# 핵심: 2차원 → 3차원으로 "변환"
# === 3. 전치 ===
print(f"\n=== 전치 ===")
print(f"W:\n{W}")
print(f"W.T:\n{W.T}") # 3×2 → 2×3
# === 4. 행렬 곱 ===
A = np.array([[1, 2], [3, 4]]) # 2×2
B = np.array([[5, 6], [7, 8]]) # 2×2
print(f"\n=== 행렬 곱 ===")
print(f"A @ B =\n{A @ B}")
print(f"B @ A =\n{B @ A}")
print("→ A@B ≠ B@A! 행렬 곱은 교환법칙이 성립하지 않음")
# === 5. softmax — 생성형 AI의 핵심 함수 ===
def softmax(x):
"""실수 벡터를 확률 분포로 변환"""
exp_x = np.exp(x - np.max(x)) # 수치 안정성
return exp_x / exp_x.sum()
logits = np.array([2.0, 1.0, 0.1]) # 모델의 원시 출력
probs = softmax(logits)
print(f"\n=== softmax ===")
print(f"입력 (logits): {logits}")
print(f"출력 (확률): {probs}")
print(f"합계: {probs.sum():.4f}") # 1.0000
print("→ 가장 큰 값(2.0)이 가장 높은 확률(0.659)로 변환됨")
# === 6. 내적 → 어텐션 점수 미리보기 ===
# "나는 은행에서 돈을 찾았다"
q_bank = np.array([0.8, 0.1]) # "은행"의 쿼리 벡터
k_money = np.array([0.9, 0.2]) # "돈"의 키 벡터
k_sat = np.array([0.1, 0.7]) # "앉았다"의 키 벡터
score_money = np.dot(q_bank, k_money)
score_sat = np.dot(q_bank, k_sat)
print(f"\n=== 어텐션 점수 미리보기 ===")
print(f"'은행'과 '돈'의 관련도: {score_money:.2f}")
print(f"'은행'과 '앉았다'의 관련도: {score_sat:.2f}")
print(f"→ '돈'과 더 관련 높음 → 금융기관으로 해석")
핵심 체크포인트
- ✅ 행렬 곱 (W @ x): 벡터를 다른 차원으로 변환 — 신경망의 기본 연산
- ✅ 내적 (dot product): 두 벡터의 유사도 — 어텐션의 Q·K 계산에 사용
- ✅ softmax: 숫자 → 확률 분포(합=1) — 다음 토큰 확률, 어텐션 가중치에 사용
- ✅ 행렬 곱은 교환법칙 안 됨: A@B ≠ B@A
0.3 머신러닝 기초
한 줄 요약
머신러닝은 "데이터에서 규칙을 스스로 학습"하는 것이며, 생성형 AI의 자기 지도 학습은 "레이블 없이도 학습할 수 있는" 혁신이다.
쉬운 설명 (비유로 풀어쓰기)
규칙 기반 vs 머신러닝 = 요리 - 규칙 기반: "소금 5g, 후추 3g, 180도 20분" → 레시피대로만 - 머신러닝: 100개 요리를 먹어보고 → "이런 조합이 맛있구나" 스스로 깨달음
지도 학습 = 선생님이 정답을 알려주는 시험 공부 - "이 이메일은 스팸이야" (정답 제공) → 패턴 학습 → 새 이메일 분류
비지도 학습 = 정답 없이 스스로 분류 - 동물원에서 동물들을 보고 "얘네는 비슷하게 생겼네" → 그룹핑
자기 지도 학습 = 빈칸 채우기 퀴즈 - "나는 오늘 ___에서 밥을 먹었다" → "식당"을 맞추면서 언어를 배움 - GPT가 바로 이 방식! 인터넷 텍스트에서 다음 단어를 예측하며 학습
실무 예제
import numpy as np
# === 1. 선형 회귀: 경사 하강법으로 처음부터 구현 ===
# y = 3x + 2 를 학습해보자
np.random.seed(42)
# 학습 데이터 생성
X = np.random.uniform(0, 10, 50)
y = 3 * X + 2 + np.random.normal(0, 1, 50) # 약간의 노이즈
# 매개변수 초기화
w = 0.0 # 가중치 (기울기)
b = 0.0 # 편향 (절편)
lr = 0.001 # 학습률
print("=== 경사 하강법으로 선형 회귀 학습 ===")
for epoch in range(100):
# 예측
y_pred = w * X + b
# 손실 (MSE)
loss = np.mean((y - y_pred) ** 2)
# 기울기 계산
dw = -2 * np.mean(X * (y - y_pred))
db = -2 * np.mean(y - y_pred)
# 매개변수 업데이트
w -= lr * dw
b -= lr * db
if epoch % 20 == 0:
print(f" 에포크 {epoch:3d}: 손실={loss:.4f}, w={w:.4f}, b={b:.4f}")
print(f"\n 최종: w={w:.4f} (정답: 3.0), b={b:.4f} (정답: 2.0)")
# === 2. 과적합 시연 ===
# 데이터: 5개 점
x_train = np.array([1, 2, 3, 4, 5], dtype=float)
y_train = np.array([2.1, 4.0, 5.8, 8.1, 9.9])
# 1차 (적절): y = ax + b
coeffs_1 = np.polyfit(x_train, y_train, 1)
# 4차 (과적합): 데이터 5개에 매개변수 5개
coeffs_4 = np.polyfit(x_train, y_train, 4)
# 새 데이터에서 비교
x_test = np.array([2.5, 3.5])
y_test = np.array([5.0, 7.0]) # 실제 값
pred_1 = np.polyval(coeffs_1, x_test)
pred_4 = np.polyval(coeffs_4, x_test)
print("\n=== 과적합 vs 적절한 모델 ===")
print(f" 1차 모델 예측: {pred_1} (실제: {y_test})")
print(f" 4차 모델 예측: {pred_4} (실제: {y_test})")
print(f" 1차 오차: {np.mean(np.abs(pred_1 - y_test)):.4f}")
print(f" 4차 오차: {np.mean(np.abs(pred_4 - y_test)):.4f}")
print("→ 4차 모델은 학습 데이터에 완벽히 맞지만 새 데이터에서 오차가 클 수 있음")
# === 3. 자기 지도 학습 시뮬레이션 ===
sentence = "나는 오늘 학교에서 수학을 공부했다"
words = sentence.split()
print(f"\n=== 자기 지도 학습 (다음 단어 예측) ===")
for i in range(len(words) - 1):
context = " ".join(words[:i+1])
target = words[i+1]
print(f" 입력: '{context}' → 예측 대상: '{target}'")
print("→ 레이블 없이 텍스트 자체에서 학습 신호를 만듦!")
핵심 체크포인트
- ✅ 경사 하강법: 기울기 방향의 반대로 매개변수를 조금씩 업데이트
- ✅ 과적합: 매개변수가 데이터보다 많으면 위험 → 검증 데이터로 확인
- ✅ 자기 지도 학습: 레이블 없이 데이터 자체에서 학습 → 생성형 AI의 핵심 원동력
0.4 Python/numpy 기초
한 줄 요약
numpy는 "빠른 숫자 배열 연산 도구"이며, ML/딥러닝 코드의 거의 모든 연산이 numpy 위에서 동작한다.
쉬운 설명 (비유로 풀어쓰기)
Python 리스트 = 한 줄로 선 사람들 (하나씩 처리) numpy 배열 = 군대 대형 (한 번에 전체 이동)
for문으로 100만 개를 하나씩 더하면 느리지만, numpy는 내부적으로 C언어로 한 번에 처리!
실무 예제
import numpy as np
# === 1. 배열 생성 ===
print("=== 배열 생성 ===")
a = np.array([1, 2, 3, 4, 5])
print(f"np.array: {a}")
print(f"np.zeros(3): {np.zeros(3)}")
print(f"np.ones((2,3)):\n{np.ones((2,3))}")
print(f"np.arange(0, 10, 2): {np.arange(0, 10, 2)}")
print(f"np.linspace(0, 1, 5): {np.linspace(0, 1, 5)}")
# === 2. 인덱싱과 슬라이싱 ===
print("\n=== 인덱싱 ===")
m = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(f"행렬:\n{m}")
print(f"m[0, 1] = {m[0, 1]}") # 2
print(f"m[:, 0] = {m[:, 0]}") # 1열: [1, 4, 7]
print(f"m[1:, :2] =\n{m[1:, :2]}") # 2~3행, 1~2열
# === 3. 연산 ===
print("\n=== 배열 연산 ===")
x = np.array([1, 2, 3, 4])
print(f"x = {x}")
print(f"x + 10 = {x + 10}") # 브로드캐스팅
print(f"x ** 2 = {x ** 2}") # 원소별 제곱
print(f"np.sum(x) = {np.sum(x)}")
print(f"np.mean(x) = {np.mean(x)}")
print(f"np.max(x) = {np.max(x)}")
print(f"np.argmax(x) = {np.argmax(x)}") # 최대값의 인덱스
# === 4. 브로드캐스팅 ===
print("\n=== 브로드캐스팅 ===")
A = np.array([[1, 2, 3],
[4, 5, 6]]) # 2×3
b = np.array([10, 20, 30]) # 1×3
print(f"A (2×3):\n{A}")
print(f"b (3,): {b}")
print(f"A + b =\n{A + b}") # b가 자동으로 2×3으로 확장!
# === 5. reshape ===
print("\n=== reshape ===")
flat = np.arange(12)
print(f"1D: {flat}")
print(f"reshape(3,4):\n{flat.reshape(3, 4)}")
print(f"reshape(2,2,3):\n{flat.reshape(2, 2, 3)}")
# === 6. 랜덤 ===
print("\n=== 랜덤 ===")
np.random.seed(42) # 재현 가능한 결과
print(f"randn(5): {np.random.randn(5)}")
print(f"randint(1, 7, 10): {np.random.randint(1, 7, 10)}") # 주사위 10번
# === 7. ML 실전 패턴 ===
print("\n=== ML 실전 패턴 ===")
# one-hot 인코딩
labels = np.array([0, 2, 1, 0, 2])
n_classes = 3
one_hot = np.eye(n_classes)[labels]
print(f"labels: {labels}")
print(f"one-hot:\n{one_hot}")
# 정규화 (0~1 스케일링)
data = np.array([100, 200, 300, 400, 500])
normalized = (data - data.min()) / (data.max() - data.min())
print(f"\n원본: {data}")
print(f"정규화: {normalized}")
# argmax로 예측 클래스 결정
probs = np.array([[0.1, 0.7, 0.2],
[0.8, 0.1, 0.1]])
predictions = np.argmax(probs, axis=1)
print(f"\n확률: {probs}")
print(f"예측 클래스: {predictions}") # [1, 0]
핵심 체크포인트
- ✅ numpy 배열은 같은 타입의 데이터만 → 빠른 연산 가능
- ✅ 브로드캐스팅: 크기가 다른 배열도 자동으로 맞춰서 연산
- ✅ ML 필수 패턴: one-hot 인코딩, 정규화, argmax → 거의 모든 모델에서 사용
0.5 딥러닝 기초
한 줄 요약
신경망은 "행렬 곱 + 활성화 함수"를 반복 쌓은 것이며, 역전파로 가중치를 업데이트하여 학습한다.
쉬운 설명 (비유로 풀어쓰기)
신경망 = 여러 단계의 필터 체인 - 입력 사진 → [필터1: 윤곽 추출] → [필터2: 패턴 인식] → [필터3: 분류] → "고양이" - 각 필터 = 하나의 층(layer), 필터의 설정값 = 가중치(weight)
활성화 함수 = 문턱 - 입력이 일정 수준을 넘으면 "발동!" (비선형성 추가) - 없으면 아무리 층을 쌓아도 결국 하나의 행렬 곱과 같음
역전파 = 시험 채점 후 복습 1. 순전파: 시험 봄 (예측) 2. 채점: 틀린 문제 확인 (손실 계산) 3. 역전파: 어디서 틀렸는지 역추적 (기울기 계산) 4. 복습: 약한 부분 강화 (가중치 업데이트)
실무 예제
import numpy as np
# === 1. 활성화 함수 비교 ===
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def relu(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
x = np.array([-3, -1, 0, 1, 3])
print("=== 활성화 함수 비교 ===")
print(f"입력: {x}")
print(f"Sigmoid: {sigmoid(x)}")
print(f"ReLU: {relu(x)}")
print(f"Tanh: {tanh(x)}")
print("→ Sigmoid: 0~1 출력 (확률), ReLU: 0 이하 차단 (가장 인기), Tanh: -1~1")
# === 2. 퍼셉트론으로 AND 게이트 ===
print("\n=== 퍼셉트론: AND 게이트 ===")
# AND: 둘 다 1일 때만 1
w = np.array([0.5, 0.5])
b = -0.7
for x1, x2 in [(0,0), (0,1), (1,0), (1,1)]:
x = np.array([x1, x2])
output = 1 if np.dot(w, x) + b > 0 else 0
print(f" AND({x1}, {x2}) = {output}")
# === 3. 2층 신경망으로 XOR 풀기 (핵심 예제!) ===
print("\n=== 2층 신경망: XOR 학습 ===")
# XOR: 같으면 0, 다르면 1
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])
np.random.seed(42)
# 가중치 초기화
W1 = np.random.randn(2, 4) * 0.5 # 입력(2) → 은닉(4)
b1 = np.zeros((1, 4))
W2 = np.random.randn(4, 1) * 0.5 # 은닉(4) → 출력(1)
b2 = np.zeros((1, 1))
lr = 0.5
for epoch in range(5000):
# --- 순전파 ---
z1 = X @ W1 + b1 # 은닉층 입력
a1 = sigmoid(z1) # 은닉층 출력 (활성화)
z2 = a1 @ W2 + b2 # 출력층 입력
a2 = sigmoid(z2) # 최종 출력
# --- 손실 (MSE) ---
loss = np.mean((y - a2) ** 2)
# --- 역전파 ---
# 출력층
d2 = (a2 - y) * a2 * (1 - a2) # sigmoid 미분
dW2 = a1.T @ d2
db2 = np.sum(d2, axis=0, keepdims=True)
# 은닉층
d1 = (d2 @ W2.T) * a1 * (1 - a1)
dW1 = X.T @ d1
db1 = np.sum(d1, axis=0, keepdims=True)
# --- 업데이트 ---
W2 -= lr * dW2
b2 -= lr * db2
W1 -= lr * dW1
b1 -= lr * db1
if epoch % 1000 == 0:
print(f" 에포크 {epoch}: 손실 = {loss:.6f}")
# 최종 결과
print(f"\n 최종 예측:")
for i in range(4):
pred = a2[i, 0]
print(f" XOR({X[i,0]}, {X[i,1]}) = {pred:.4f} (목표: {y[i,0]})")
# === 4. 손실 함수 비교 ===
print("\n=== 손실 함수 ===")
y_true = np.array([1, 0, 1, 1])
y_pred = np.array([0.9, 0.1, 0.8, 0.6])
# MSE (평균 제곱 오차) — 회귀에서 주로 사용
mse = np.mean((y_true - y_pred) ** 2)
print(f" MSE: {mse:.4f}")
# Cross-Entropy — 분류에서 주로 사용
eps = 1e-15 # log(0) 방지
ce = -np.mean(y_true * np.log(y_pred + eps) + (1-y_true) * np.log(1-y_pred + eps))
print(f" Cross-Entropy: {ce:.4f}")
print("→ 분류 문제에서는 Cross-Entropy가 더 효과적 (기울기가 더 잘 전달됨)")
핵심 체크포인트
- ✅ 신경망 = 행렬 곱(W @ x + b) + 활성화 함수를 반복 쌓은 것
- ✅ 활성화 함수 없이는 깊이를 쌓아도 의미 없음 (비선형성이 필수)
- ✅ 역전파: 출력 오차 → 각 가중치가 오차에 기여한 정도 계산 → 업데이트
- ✅ XOR 문제: 단층 퍼셉트론으로 불가능 → 은닉층 추가로 해결 (딥러닝의 핵심!)
연습문제
연습문제 1: 베이즈 정리 응용
문제: 어떤 공장에서 불량품 비율이 2%이다. 검사기의 불량 탐지율은 95%, 오탐률은 3%이다. 검사기가 "불량"이라고 판정했을 때 실제 불량일 확률을 구하시오.
풀이:
P(불량) = 0.02
P(양성|불량) = 0.95
P(양성|정상) = 0.03
P(양성) = 0.95 × 0.02 + 0.03 × 0.98 = 0.019 + 0.0294 = 0.0484
P(불량|양성) = (0.95 × 0.02) / 0.0484 = 0.019 / 0.0484 ≈ 0.3926
답: 약 39.3% — 검사기가 불량이라고 해도 실제 불량일 확률은 39%밖에 안 됨!
연습문제 2: softmax 직접 계산
문제: 입력 벡터 [3.0, 1.0, -1.0]에 대해 softmax를 수동으로 계산하시오.
풀이:
exp(3.0) = 20.09, exp(1.0) = 2.72, exp(-1.0) = 0.37
합계 = 23.18
softmax = [20.09/23.18, 2.72/23.18, 0.37/23.18] = [0.867, 0.117, 0.016]
→ 가장 큰 값이 86.7%의 확률로 변환됨
연습문제 3: XOR 신경망 분석
문제: 위 XOR 예제에서 은닉층 뉴런을 2개로 줄이면 어떻게 되는가? 학습이 가능한지 실험하고 이유를 설명하시오.
풀이: W1을 (2, 2)로 바꾸고 실행하면 학습이 불안정하거나 실패할 수 있다. XOR은 최소 2개의 은닉 뉴런이 필요하지만, 초기값에 따라 수렴이 어려울 수 있다. 4개로 늘리면 여유가 생겨 안정적으로 학습된다. 이것이 "모델 역량(capacity)"의 개념이다.
부록 A: 용어 사전
| 한글 | 영문 | 의미 |
|---|---|---|
| 확률 | Probability | 사건이 일어날 가능성 (0~1) |
| 조건부 확률 | Conditional Probability | 조건 하의 확률 P(A|B) |
| 베이즈 정리 | Bayes' Theorem | 사후확률 = 우도 × 사전확률 / 증거 |
| 벡터 | Vector | 크기와 방향을 가진 1차원 배열 |
| 행렬 | Matrix | 2차원 숫자 배열 |
| 내적 | Dot Product | 벡터 유사도 계산 |
| softmax | Softmax | 실수 → 확률 분포 변환 |
| 경사 하강법 | Gradient Descent | 기울기 반대 방향으로 매개변수 업데이트 |
| 역전파 | Backpropagation | 오차를 역방향 전파하여 기울기 계산 |
| 활성화 함수 | Activation Function | 비선형 변환 (Sigmoid, ReLU 등) |
| 과적합 | Overfitting | 학습 데이터에만 맞고 일반화 실패 |
부록 B: 핵심 비교표
학습 방식 비교
| 방식 | 정답 필요? | 핵심 아이디어 | 예시 |
|---|---|---|---|
| 지도 학습 | 필요 | 정답 보고 배움 | 이메일 스팸 분류 |
| 비지도 학습 | 불필요 | 패턴 스스로 발견 | 고객 세그먼테이션 |
| 자기 지도 학습 | 불필요 | 데이터 자체에서 학습 | GPT 다음 단어 예측 |
활성화 함수 비교
| 함수 | 출력 범위 | 장점 | 단점 |
|---|---|---|---|
| Sigmoid | 0~1 | 확률 해석 가능 | 기울기 소실 문제 |
| ReLU | 0~∞ | 계산 빠름, 기울기 소실 없음 | 음수 입력 시 죽은 뉴런 |
| Tanh | -1~1 | 0 중심 출력 | 기울기 소실 문제 |
부록 C: 추천 참고 자료 & 링크
| 주제 | 자료 | 링크 |
|---|---|---|
| numpy | NumPy 공식 튜토리얼 | https://numpy.org/doc/stable/user/quickstart.html |
| 선형대수 | 3Blue1Brown (시각적 설명) | https://www.3blue1brown.com/topics/linear-algebra |
| 확률/통계 | Khan Academy | https://www.khanacademy.org/math/statistics-probability |
| 딥러닝 | Deep Learning Book (Goodfellow) | https://www.deeplearningbook.org/ |
| PyTorch | PyTorch 공식 튜토리얼 | https://pytorch.org/tutorials/ |
| 신경망 | Neural Networks and Deep Learning (Michael Nielsen) | http://neuralnetworksanddeeplearning.com/ |
클릭하거나 Space를 눌러 뒤집기